//#########################################################################
//                              WackiEngie
//           Copyright (c) 2014 Marc Wacker. All rights reserved.
//#########################################################################

#include "WeInput.h"
#include "WePlatform.h"
#include "WeTime.h"
#include "WeDebug.h"

namespace WackiEngine
{

	Input::Input()
		: _keyRepeatDelta(0.5f)
	{
		// connect os callbacks
		gPlatform().mouseMove.connect(this, &Input::onMouseMove);
		gPlatform().mouseDown.connect(this, &Input::onMouseDown);
		gPlatform().mouseUp.connect(this, &Input::onMouseUp);
		gPlatform().mouseDoubleClick.connect(this, &Input::onMouseDoubleClick);
		gPlatform().mouseWheel.connect(this, &Input::onMouseWheel);
		gPlatform().charInput.connect(this, &Input::onCharInput);
		gPlatform().keyboardDown.connect(this, &Input::onKeyboardDown);
		gPlatform().keyboardUp.connect(this, &Input::onKeyboardUp);
	}

	Input::~Input()
	{
		// clear connections (if even needed, check if this is needed)
	}

	void Input::update()
	{
		// reset changed this frame flags
		for(auto device : _devices) {
			for(auto key : device.keys)
				key.changedThisFrame = false;
		}

		// update connected device handlers
		for(auto device : _deviceHandlers)
			device->update();

		// update mouse delta and fire a changed event if its different;
		Vector2f curMouseDelta = _mousePos - _mousePrevPos;

		if(curMouseDelta.x != getAxis(KC_Mouse_X)) {
			onAxisMoved(0, curMouseDelta.x, KC_Mouse_X);
		}
		if(curMouseDelta.y != getAxis(KC_Mouse_Y)) {
			onAxisMoved(0, curMouseDelta.y, KC_Mouse_Y);
		}

		_mousePrevPos = _mousePos;

		// generate key repeat events
		if(!keyInput.empty())
		{
			for(uint32 deviceId = 0; deviceId < _devices.size(); ++deviceId)
			{
				DeviceData& device = _devices[deviceId];

				for(uint32 key = 1; key < KC_Count; ++key)
				{
					KeyState& keyState = device.keys[key];
					if(!keyState.isDown)
						continue;

					if(gTime().getTime() - keyState.lastDown >= _keyRepeatDelta) {
						KeyEvent keyEvent((KeyCode)key, KE_Repeat, _modifierState, deviceId);
						keyInput(keyEvent);
					}
				}
			}
		}
	}

	float Input::getAxis(KeyCode axis, uint32 deviceId) const
	{
		if(_devices.size() <= deviceId)
			return 0.0f;

		return _devices[deviceId].axes[axis];
	}

	bool Input::getKey(KeyCode key, uint32 deviceId) const
	{
		if(_devices.size() <= deviceId)
			return false;

		return _devices[deviceId].keys[key].isDown;
	}

	bool Input::getKeyDown(KeyCode key, uint32 deviceId) const
	{
		if(_devices.size() <= deviceId)
			return false;

		const KeyState& ks = _devices[deviceId].keys[key];

		return ks.isDown && ks.changedThisFrame;
	}

	bool Input::getKeyUp(KeyCode key, uint32 deviceId) const
	{
		if(_devices.size() <= deviceId)
			return false;

		const KeyState& ks = _devices[deviceId].keys[key];

		return !ks.isDown && ks.changedThisFrame;
	}
	

	void Input::registerInputDeviceHandler(std::shared_ptr<InputDeviceHandler> idh)
	{
		if(_deviceHandlers.find(idh) != _deviceHandlers.end())
			return;


		LOGINFO("Adding device handler.");

		// add to the list
		_deviceHandlers.insert(idh);

		// connect callbacks
		idh->keyDown.connect(this, &Input::onKeyDown);
		idh->keyUp.connect(this, &Input::onKeyUp);
		idh->axisMoved.connect(this, &Input::onAxisMoved);
	}

	// this callback is only called if a key changed state from up to down! repeat's dont fire the devicehandler events!
	void Input::onKeyDown(uint32 deviceId, KeyCode keyCode)
	{
		// make sure we have enough space for the device
		while(deviceId >= _devices.size())
			_devices.push_back(DeviceData());

		KeyState& keyState = _devices[deviceId].keys[keyCode];

		// update the axis value for this key
		_devices[deviceId].axes[keyCode] = 1.0f;
		
		// time passed since last time this key has been pressed
		double pressDelta = gTime().getTime() - keyState.lastDown;

		// state changed this frame because only state changes fire the event
		keyState.changedThisFrame = true;
		keyState.isDown = true;
		keyState.lastDown = gTime().getTime();

		if(!keyInput.empty())
		{
			{
				// TODO: fire event
				// Do we want to send repeat events? if so we need to do it in the update function
				KeyEvent e(keyCode, KE_Press, 0, deviceId);

				keyInput(e);
			}

			// TODO: add configurable doubleclick delta time!
			if(pressDelta < 0.5f)
			{
				KeyEvent e(keyCode, KE_DoubleClick, 0, deviceId);

				keyInput(e);
			}
		}
	}

	void Input::onKeyUp(uint32 deviceId, KeyCode keyCode)
	{
		// make sure we have enough space for the device
		while(deviceId >= _devices.size())
			_devices.push_back(DeviceData());

		KeyState& keyState = _devices[deviceId].keys[keyCode];

		// update the axis value for this key
		_devices[deviceId].axes[keyCode] = 0.0f;

		keyState.changedThisFrame = true;
		keyState.isDown = false;

		if(!keyInput.empty())
		{
			KeyEvent e(keyCode, KE_Release, 0, deviceId);
			keyInput(e);
		}
		
	}

	void Input::onAxisMoved(uint32 deviceId, float value, KeyCode keyCode)
	{
		// make sure we have enough space for the device
		while(deviceId >= _devices.size())
			_devices.push_back(DeviceData());
		
		_devices[deviceId].axes[keyCode] = value;
	}



	// Platform (OS) callbacks
	void Input::onMouseMove(const PointerEvent& e)
	{
		// update axis state for the mouse
		// update the mouse position
		_mousePos.set(e.x, e.y);

		// fire the input mouse event
		if(!mouseMove.empty())
			mouseMove(e);
	}
	void Input::onMouseDown(const PointerEvent& e)
	{
		if(!mouseDown.empty())
			mouseDown(e);

		// also update the button in our state for deviceId 0 (mouse and keyboard)
		onKeyDown(0, e.button);
	}
	void Input::onMouseUp(const PointerEvent& e)
	{
		if(!mouseUp.empty())
			mouseUp(e);

		// also update the button in our state for deviceId 0 (mouse and keyboard)
		onKeyUp(0, e.button);
	}
	void Input::onMouseDoubleClick(const PointerEvent& e)
	{
		if(!mouseDoubleClick.empty())
			mouseDoubleClick(e);
	}
	void Input::onMouseWheel(const PointerEvent& e)
	{
		// relay event
		if(!scrollWheel.empty())
			scrollWheel(e);

		// generate key events from the wheel
		if(e.deltaX < 0.0f) {
			onKeyDown(0, KC_Mouse_WheelLeft);
			onKeyUp(0, KC_Mouse_WheelLeft);
		}
		else if(e.deltaX > 0.0f) {
			onKeyDown(0, KC_Mouse_WheelRight);
			onKeyUp(0, KC_Mouse_WheelRight);
		}
		if(e.deltaY < 0.0f) {
			onKeyDown(0, KC_Mouse_WheelDown);
			onKeyUp(0, KC_Mouse_WheelDown);
		}
		else if(e.deltaY > 0.0f) {
			onKeyDown(0, KC_Mouse_WheelUp);
			onKeyUp(0, KC_Mouse_WheelUp);
		}
	}

	void Input::onCharInput(uint32 character)
	{
		if(characterInput.empty())
			return;

		CharInputEvent e(character);
		characterInput(e);
	}
	void Input::onKeyboardDown(KeyCode keyCode)
	{
		// the os keyboard is just relayed to the normal key callbacks with deviceId 0
		onKeyDown(0, keyCode);
	}
	void Input::onKeyboardUp(KeyCode keyCode)
	{
		// the os keyboard is just relayed to the normal key callbacks with deviceId 0
		onKeyUp(0, keyCode);
	}

	Input& gInput() 
	{
		return Input::instance();
	}

}